package cn.ecnu.controller;

import cn.ecnu.pojo.Conference;
import cn.ecnu.pojo.Favorite;
import cn.ecnu.service.ConferenceService;
import cn.ecnu.service.FavoriteService;
import cn.ecnu.utils.R;
import cn.ecnu.utils.Result;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/conference")
@Api(tags = "Conference Management APIs", description = "APIs for managing journals")
public class ConferenceController {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ConferenceService conferenceService;

    @Autowired
    private FavoriteService favoriteService;

    String redisViewKey = "top10_conferences_views";
    String redisFollowKey = "top10_conferences_follows";

    @ApiOperation(value = "Create a new conference", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully created conference"),
            @ApiResponse(code = 400, message = "Bad Request")
    })
    @PostMapping
    public Result<?> createConference(@ApiParam(value = "Conference object to be created", required = true) @RequestBody Conference conference) {
        boolean saved = conferenceService.save(conference);
        if (saved) {
            return Result.ok();
        } else {
            return Result.error("Failed to create conference");
        }
    }

    @ApiOperation(value = "Get a list of all conferences", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully retrieved list"),
            @ApiResponse(code = 400, message = "Bad Request")
    })
    @GetMapping
    public Result<List<Conference>> getAllConferences() {
        List<Conference> conferences = conferenceService.list();
        return Result.ok(conferences);
    }

    @ApiOperation(value = "Get a paginated list of conferences", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully retrieved paginated list"),
            @ApiResponse(code = 400, message = "Bad Request")
    })
    @GetMapping("/page")
    public Result<IPage<Conference>> getConferencesPage(@ApiParam(value = "Page number", required = true) @RequestParam int page,
                                @ApiParam(value = "Page size", required = true) @RequestParam int size) {
        Page<Conference> conferencePage = new Page<>(page, size);
        IPage<Conference> resultPage = conferenceService.page(conferencePage, new QueryWrapper<>());
        return Result.ok(resultPage);
    }

    @ApiOperation(value = "Get a conference by ID and increment its view count", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully retrieved conference"),
            @ApiResponse(code = 404, message = "Conference not found")
    })
    @GetMapping("/{id}")
    public Result<Conference> getConferenceById(@ApiParam(value = "ID of the conference to be retrieved", required = true) @PathVariable int id) {
        conferenceService.incrementViews(id);
        Conference conference = conferenceService.getById(id);
        if (conference != null) {
            return Result.ok(conference);
        } else {
            return Result.error("Conference not found");
        }
    }

    @ApiOperation(value = "Update a conference", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully updated conference"),
            @ApiResponse(code = 404, message = "Conference not found")
    })
    @PutMapping("/{id}")
    public Result<?> updateConference(@ApiParam(value = "ID of the conference to be updated", required = true) @PathVariable Integer id,
                              @ApiParam(value = "Updated conference object", required = true) @RequestBody Conference conference) {
        conference.setConferenceId(id);
        boolean updated = conferenceService.updateById(conference);
        if (updated) {
            return Result.ok(conference);
        } else {
            return Result.error("Failed to update conference");
        }
    }

    @ApiOperation(value = "Delete a conference", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully deleted conference"),
            @ApiResponse(code = 404, message = "Conference not found")
    })
    @DeleteMapping("/{id}")
    public Result<?> deleteConference(@ApiParam(value = "ID of the conference to be deleted", required = true) @PathVariable Long id) {
        boolean removed = conferenceService.removeById(id);
        if (removed) {
            return Result.ok("Conference deleted successfully");
        } else {
            return Result.error("Failed to delete conference");
        }
    }

    @ApiOperation(value = "Search conferences by keyword", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully retrieved list of conferences"),
            @ApiResponse(code = 400, message = "Bad Request")
    })
    @GetMapping("/search")
    public Result<List<Conference>> searchConferences(@ApiParam(value = "Keyword for searching conferences", required = true) HttpServletRequest request) {
        QueryWrapper<Conference> queryWrapper = new QueryWrapper<>();
        String keyword = request.getParameter("keyword");
        queryWrapper.apply("LOWER(title) LIKE {0} OR LOWER(description) LIKE {0} OR LOWER(short_name) LIKE {0}",
                "%" + keyword.toLowerCase() + "%");
        List<Conference> conferences = conferenceService.list(queryWrapper);
        return Result.ok(conferences);
    }

    @ApiOperation(value = "Get favorite conferences by user ID", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully retrieved list of favorite conferences"),
            @ApiResponse(code = 404, message = "User not found or no favorite conferences")
    })
    @GetMapping("/favorites/{userId}")
    public Result<List<Conference>> getFavoriteConferencesByUserId(@ApiParam(value = "ID of the user to get favorite conferences", required = true) @PathVariable Long userId) {
        QueryWrapper<Favorite> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId).isNotNull("conference_id");
        List<Favorite> favorites = favoriteService.list(queryWrapper);
        List<Long> conferenceIds = favorites.stream().map(Favorite::getConferenceId).collect(Collectors.toList());

        if (conferenceIds.isEmpty()) {
            return Result.ok(Collections.emptyList());
        }

        List<Conference> conferences = conferenceService.listByIds(conferenceIds);
        return Result.ok(conferences);
    }

    @ApiOperation(value = "Get top 10 conferences by views, first search in redis", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully retrieved top 10 conferences by views"),
            @ApiResponse(code = 500, message = "Internal Server Error")
    })
    @GetMapping("/top10/views")
    public Result<List<Conference>> getTop10ConferencesByViews() {
        List<Conference> conferences = (List<Conference>) redisTemplate.opsForValue().get(redisViewKey);
        if (conferences == null) {
            conferences = conferenceService.getTop10ConferencesByViews();
            redisTemplate.opsForValue().set(redisViewKey, conferences);
            redisTemplate.expire(redisViewKey, 30, TimeUnit.MINUTES);
        }
        return Result.ok(conferences);
    }

    @ApiOperation(value = "Get top 10 conferences by follows, first search in redis", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully retrieved top 10 conferences by follows"),
            @ApiResponse(code = 500, message = "Internal Server Error")
    })
    @GetMapping("/top10/follows")
    public Result<List<Conference>> getTop10ConferencesByFollows() {
        List<Conference> conferences = (List<Conference>) redisTemplate.opsForValue().get(redisFollowKey);
        if (conferences == null) {
            conferences = conferenceService.getTop10ConferencesByFollows();
            redisTemplate.opsForValue().set(redisFollowKey, conferences);
            redisTemplate.expire(redisFollowKey, 30, TimeUnit.MINUTES);
        }
        return Result.ok(conferences);
    }

    @ApiOperation(value = "Follow a conference and increment its follow count", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully followed conference"),
            @ApiResponse(code = 404, message = "Conference not found")
    })
    @PostMapping("/follow/{userId}/{conferenceId}")
    public Result<?> followConference(@ApiParam(value = "ID of the user who is following", required = true) @PathVariable Long userId,
                              @ApiParam(value = "ID of the conference to follow", required = true) @PathVariable Long conferenceId) {
        Favorite favorite = new Favorite();
        favorite.setUserId(userId);
        favorite.setConferenceId(conferenceId);
        favoriteService.save(favorite);
        conferenceService.incrementFollows(conferenceId);
        return Result.ok("Follow count incremented and conference followed");
    }

    @ApiOperation(value = "Unfollow a conference and decrement its follow count", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully unfollowed conference"),
            @ApiResponse(code = 404, message = "Conference not found")
    })
    @DeleteMapping("/unfollow/{userId}/{conferenceId}")
    public Result<?> unfollowConference(@ApiParam(value = "ID of the user who is unfollowing", required = true) @PathVariable Long userId,
                                @ApiParam(value = "ID of the conference to unfollow", required = true) @PathVariable Long conferenceId) {
        QueryWrapper<Favorite> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId).eq("conference_id", conferenceId);
        favoriteService.remove(queryWrapper);
        conferenceService.decrementFollows(conferenceId);
        return Result.ok("Follow count decremented and conference unfollowed");
    }

    @ApiOperation(value = "Get conferences followed by a user", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully retrieved list of followed conferences"),
            @ApiResponse(code = 404, message = "User not found or no followed conferences")
    })
    @GetMapping("/user/{userId}/favorites")
    public Result<List<Conference>> getUserFavoriteConferences(@ApiParam(value = "ID of the user to get followed conferences", required = true) @PathVariable Long userId) {
        QueryWrapper<Favorite> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId).isNotNull("conference_id");
        List<Favorite> favorites = favoriteService.list(queryWrapper);
        List<Long> conferenceIds = favorites.stream().map(Favorite::getConferenceId).collect(Collectors.toList());
        if (conferenceIds.isEmpty()) {
            return Result.ok(Collections.emptyList());
        }
        List<Conference> conferences = conferenceService.listByIds(conferenceIds);
        return Result.ok(conferences);
    }

    @ApiOperation(value = "Check if a user is following a conference", response = Result.class)
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "Successfully checked if user is following conference"),
            @ApiResponse(code = 404, message = "Conference not found")
    })
    @GetMapping("/{conferenceId}/is-followed/{userId}")
    public Result<?> isUserFollowingConference(
            @ApiParam(value = "ID of the conference to check", required = true) @PathVariable Long conferenceId,
            @ApiParam(value = "ID of the user to check", required = true) @PathVariable Long userId) {
        boolean isFollowing = favoriteService.isUserFollowingConference(userId, conferenceId);
        return Result.ok(isFollowing);
    }
}
